﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Threading;

namespace SlamContext.Cache
{
	internal class CacheManager
	{
		private List<string> _threadCachedObjects = new List<string>();
        private Dictionary<string, object> _applicationCachedObjects = new Dictionary<string, object>();

		private static System.Web.Caching.CacheItemRemovedCallback OnCacheItemRemoved;

		public void Add(string name, object value, CacheScope scope)
		{
			if (scope == CacheScope.Thread)
			{
				string threadItemName = String.Format("{0}t{1}t", name, Thread.CurrentThread.ManagedThreadId);
				if (HttpRuntime.Cache != null)
				{
					OnCacheItemRemoved = CacheItemRemovedCallback;
					if (HttpRuntime.Cache.Get(threadItemName) == null && value != null)
					{
						HttpRuntime.Cache.Insert(threadItemName, value, null, DateTime.Now.AddMinutes(5), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, OnCacheItemRemoved);
					}
					else if (HttpRuntime.Cache.Get(threadItemName) != null && value != null)
					{
						HttpRuntime.Cache.Remove(threadItemName);
						HttpRuntime.Cache.Insert(threadItemName, value, null, DateTime.Now.AddMinutes(5), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, OnCacheItemRemoved);
					}
					else
					{
						HttpRuntime.Cache.Remove(threadItemName);
					}
				}
				else
				{
					Thread.SetData(Thread.GetNamedDataSlot(threadItemName), value);
				}

				if (!_threadCachedObjects.Contains(threadItemName))
				{
					_threadCachedObjects.Add(threadItemName);
				}
			}
			else
			{	
                if (!_applicationCachedObjects.ContainsKey(name))
                {
                    _applicationCachedObjects.Add(name, value);
                }
                else
                {
                    _applicationCachedObjects[name] = value;
                }
			}
		}

		private void CacheItemRemovedCallback(string key, object val, System.Web.Caching.CacheItemRemovedReason reason)
		{
			try
			{
				if (_threadCachedObjects.Contains(key))
					_threadCachedObjects.Remove(key);
			}
			catch { }
			try
			{
				if (_applicationCachedObjects.ContainsKey(key))
					_applicationCachedObjects.Remove(key);
			}
			catch { }
		}

		public bool ContainsKey(string name, CacheScope scope)
		{	
			if (scope == CacheScope.Thread)
			{
				string threadItemName = String.Format("{0}t{1}t", name, Thread.CurrentThread.ManagedThreadId);
				return _threadCachedObjects.Contains(threadItemName);
			}
			else
			{
				return _applicationCachedObjects.ContainsKey(name);
			}
		}

		public T Get<T>(string name)
		{
			T value = default(T);
			string threadItemName = String.Format("{0}t{1}t", name, Thread.CurrentThread.ManagedThreadId);
			if (_threadCachedObjects.Contains(threadItemName))
			{	
				if (HttpRuntime.Cache != null)
				{
					if (HttpRuntime.Cache.Get(threadItemName) != null)
				    {
						value = (T)HttpRuntime.Cache[threadItemName];
				    }
				}
				else
				{
					LocalDataStoreSlot storeSlot = Thread.GetNamedDataSlot(threadItemName);
					if (storeSlot != null)
					{
						value = (T)Thread.GetData(storeSlot);
					}
				}
			}
			else if (_applicationCachedObjects.ContainsKey(name))
			{
                value = (T)_applicationCachedObjects[name];
			}
			return value;
		}

		public void Clear()
		{
			Clear(CacheScope.Thread);
			Clear(CacheScope.Application);
		}

		public void Clear(CacheScope scope, string contains)
		{
			if (scope == CacheScope.Thread)
			{
				var itemsToDelete = _threadCachedObjects.Where(o => !String.IsNullOrEmpty(o) ? o.Contains(contains) : false).Select(o => new { Value = o }).ToList();
				foreach (var i in itemsToDelete)
				{
					if (HttpRuntime.Cache != null)
					{
						if (HttpRuntime.Cache != null && HttpRuntime.Cache.Get(i.Value) != null)
						{
							HttpRuntime.Cache.Remove(i.Value);
						}
					}
					else
					{
						Thread.FreeNamedDataSlot(i.Value);
					}

					if (_threadCachedObjects.Contains(i.Value))
						_threadCachedObjects.Remove(i.Value);
				}
			}
			else
			{
				var itemsToDelete = _applicationCachedObjects.Where(o => !String.IsNullOrEmpty(o.Key) ? o.Key.Contains(contains) : false).Select(o => new { Value = o }).ToList();
				foreach (var i in itemsToDelete)
				{
                    _applicationCachedObjects.Remove(i.Value.Key);
				}
			}
		}

		public void Clear(CacheScope scope)
		{
			if (scope == CacheScope.Thread)
			{
				var itemsToDelete = _threadCachedObjects.Select(o => new { Value = o }).ToList();
				foreach (var i in itemsToDelete)
				{
					if (HttpRuntime.Cache != null)
					{
						if (HttpRuntime.Cache != null && HttpRuntime.Cache.Get(i.Value) != null)
						{
							HttpRuntime.Cache.Remove(i.Value);
						}
					}
					else
					{
						Thread.FreeNamedDataSlot(i.Value);
					}

					if (_threadCachedObjects.Contains(i.Value))
						_threadCachedObjects.Remove(i.Value);
				}
			}
			else
			{
                _applicationCachedObjects.Clear();
			}
		}
	}
}
